/*
 * @(#) $Header$
 *
 * Copyright (C) 2010  Forklabs Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.crypto.md;

import java.security.MessageDigest;
import java.security.spec.AlgorithmParameterSpec;
import ca.forklabs.baselib.util.Arrays;
import ca.forklabs.baselib.util.Bits;

/**
 * Class {@code AbstractMessageDigest} provides method to operate on block of
 * data (most of them). Implementations need only to implement these methods:
 * <ul>
 *    <li>{@link #coreInit()}</code></a></li>
 *    <li>{@link #coreTransform()}</a></li>
 *    <li>{@link #coreDoFinal()}</li>
 * </ul>
 * and of course the proper methods of their respective algorithm.
 * <br><br>
 * The two most important accessor methods are :
 * <ul>
 *    <li>{@link #getBuffer()} : when called in {@link #coreTransform()}, it
 *        returns a {@link #buffer_len} {@code byte[]} filled with the bits to
 *        transform.</li>
 *    <li>{@link #getCount()} : when called, it gives the number of
 *        <strong>bytes</strong> transformed so far. The internal count is
 *        incremented at the end of {@code engineUpdate()}.</li>
 * </ul>
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.crypto.md.AbstractBlockMessageDigest">Daniel Léonard</a>
 * @version $Revision$
 */
public abstract class AbstractBlockMessageDigest extends MessageDigest implements Cloneable {

//---------------------------
// Instance variables
//---------------------------

   /** Buffer for unprocessed data. */
   private byte[] buffer;

   /** The number of <strong>bytes</strong> transformed. */
   private long count = 0L;

   /** The length in bytes of the message digest. */
   private int digest_len;

   /** The length in bytes of the internal buffer. */
   private int buffer_len;


//---------------------------
// Constructors
//---------------------------

   /**
    * Constructor.
    * @param   name   the name of the algorithm.
    * @param   buffer_len  the length in bytes of {@code buffer}.
    * @param   digest_len  the length in bytes of the digest.
    */
   protected AbstractBlockMessageDigest(String name, int buffer_len, int digest_len) {
      super(name);
      this.setup(buffer_len, digest_len);
      }


//---------------------------
// Accessors and mutators
//---------------------------

   /**
    * Changes the internal buffer.
    * @param   buffer   the internal buffer.
    */
   protected void setBuffer(byte[] buffer) {
      this.buffer = buffer;
      }

   /**
    * Gets the internal buffer.
    * @return   the internal buffer.
    */
   protected byte[] getBuffer() {
      return this.buffer;
      }

   /**
    * Changes the message digest length.
    * @param   len   the digest length.
    */
   protected void setDigestLen(int len) {
      this.digest_len = len;
      }

   /**
    * Gets the message digest length in bytes.
    * @return   the digest length.
    */
   protected int getDigestLen() {
      return this.digest_len;
      }

   /**
    * Changes the buffer length.
    * @param   len   the buffer length.
    */
   protected void setBufferLen(int len) {
      this.buffer_len = len;
      }

   /**
    * Gets the buffer length in bytes.
    * @return   the buffer length.
    */
   protected int getBufferLen() {
      return this.buffer_len;
      }

   /**
    * Changes the number of bytes processed.
    * @param   count   the number of bytes processed.
    */
   protected void setCount(long count) {
      this.count = count;
      }

   /**
    * Gets the number of bytes processed.
    * @return   the number of bytes processed.
    */
   protected long getCount() {
      return this.count;
      }


//---------------------------
// Abstract methods
//---------------------------

   /**
    * Performs the final digestions. It is called by <code>engineDigest()</code>.
    * @return   the final digestion.
    * @see   #engineDigest(byte[],int,int)
    */
   protected abstract byte[] coreDoFinal();

   /**
    * Resets the message digest. It is called by <code>engineReset()</code>.
    * @see #engineReset()
    */
   protected abstract void coreInit();

   /**
    * Performs digestion on the values in the buffer. It is called by
    * <code>engineUpdate()</code>.
    * @see #engineUpdate(byte[],int,int)
    */
   protected abstract void coreTransform();


//---------------------------
// Overridden methods from java.security.MessageDigestSPI
//---------------------------

   /**
    * Completes the hash computation by performing final operations including
    * padding.
    * @return   the array of bytes for the resulting hash value.
    * @see   #engineDigest(byte[],int,int)
    */
   @Override
   protected byte[] engineDigest() {
      int len = this.getDigestLen();
      byte[] hash = new byte[len];
      this.engineDigest(hash, 0, len);
      return hash;
      }

   /**
    * Completes the hash computation by performing final operations including
    * padding. It fills the minimum number of bytes betwen <code>len</code> and
    * <code>digest_len</code>. If an exception occurs, the final transformation
    * will not be done, so you can call it again.
    * @param   buf      the byte[] to store the digest.
    * @param   offset   the starting index in <code>buf</code>.
    * @param   len      number of bytes within <code>buf</code> allotted for the digest.
    * @return           the number of byte written in <code>buf</code>.
    */
   @Override
   protected int engineDigest(byte[] buf, int offset, int len) {
      Arrays.checkArray(buf, offset, len);

      byte[] hash = this.coreDoFinal();
      int to_copy = (len < hash.length) ? len : hash.length;
      System.arraycopy(hash, 0, buf, offset, to_copy);

      this.engineReset();

      return to_copy;
      }

   /**
    * Returns the digest length in bytes.
    * @return   the length of the message digest.
    */
   @Override
   protected final int engineGetDigestLength() {
      int len = this.getDigestLen();
      return len;
      }

   /**
    * Resets the digest for further use.
    */
   @Override
   @SuppressWarnings("hiding")
   protected void engineReset() {
      this.setCount(0L);

      byte[] buffer = this.getBuffer();
      AbstractBlockMessageDigest.zeroize(buffer);

      this.coreInit();
      }

   /**
    * Updates the digest using the specified byte.
    * @param   input   the byte to digest.
    */
   @Override
   protected void engineUpdate(byte input) {
      this.engineUpdate(new byte[] { input, }, 0, 1);
      }

   /**
    * Updates the digest using a copy of the interval of the specified array.
    * @param   input    the specified array.
    * @param   offset   starting index.
    * @param   len      number of bytes to digest.
    */
   @Override
   @SuppressWarnings("hiding")
   protected void engineUpdate(byte[] input, int offset, int len) {
      Arrays.checkArray(input, offset, len);

   // where are we in the buffer
      byte[] buffer = this.getBuffer();
      int buffer_len = this.getBufferLen();
      long count = this.getCount();
      int index = (int) (count % buffer_len);
      int remaining = buffer_len - index;

   // index in input
      int iii = offset;

   // transform as many times as possible
      if (len >= remaining) {
         System.arraycopy(input, iii, buffer, index, remaining);
         this.coreTransform();
         for (iii += remaining; (iii + (buffer_len - 1)) < len; iii += buffer_len) {
            System.arraycopy(input, iii, buffer, 0, buffer_len);
            this.coreTransform();
            }
         index = 0;
         }

   // buffer remaining input
      System.arraycopy(input, iii, buffer, index, len - (iii - offset));

   // update number of bytes transformed
      count += len;
      this.setCount(count);
      }

   /**
    * Sets up the message digest.
    * @param   buffer_len  the length in bytes of {@code buffer}.
    * @param   digest_len  the length in bytes of the digest.
    */
   @SuppressWarnings("nls")
   protected void setup(int buffer_len, int digest_len) {
      if (buffer_len <= 0) {
         String message = this.getParameterNotPositiveErrorMessage("buffer_len", buffer_len);
         throw new IllegalArgumentException(message);
         }
      if (digest_len <= 0) {
         String message = this.getParameterNotPositiveErrorMessage("digest_len", digest_len);
         throw new IllegalArgumentException(message);
         }

      this.setBuffer(new byte[buffer_len]);
      this.setBufferLen(buffer_len);
      this.setDigestLen(digest_len);
      }


//---------------------------
// Implemented method from java.lang.Cloneable
//---------------------------

   /**
    * Creates a deep copy.
    * @return   a deep copy.
    */
   @Override
   public Object clone() throws CloneNotSupportedException {
      AbstractBlockMessageDigest abmd = (AbstractBlockMessageDigest) super.clone();
      abmd.buffer = this.buffer.clone();
      return abmd;
      }


//---------------------------
// Class methods
//---------------------------

   /**
    * Performs a left rotation of the bits.
    * @param   value   the <code>int</code> to rotate.
    * @param   shift   the distance to rotate.
    * @return   the result of the rotation.
    */
   protected static int rotateLeft(int value, int shift) {
      int rotated = Bits.rotateLeft(value, shift);
      return rotated;
      }

   /**
    * Performs a right rotation of the bits.
    * @param   value   the <code>int</code> to rotate.
    * @param   shift   the distance to rotate.
    * @return   the result of the rotation.
    */
   protected static int rotateRight(int value, int shift) {
      int rotated = Bits.rotateRight(value, shift);
      return rotated;
      }

   /**
    * Performs a right rotation of the bits.
    * @param   value   the <code>long</code> to rotate.
    * @param   shift   the distance to rotate.
    * @return   the result of the rotation.
    */
   protected static long rotateRight(long value, int shift) {
      long rotated = Bits.rotateRight(value, shift);
      return rotated;
      }

   /**
    * Performs a left rotation of the bits.
    * @param   value   the <code>long</code> to rotate.
    * @param   shift   the distance to rotate.
    * @return   the result of the rotation.
    */
   protected static long rotateLeft(long value, int shift) {
      long rotated = Bits.rotateLeft(value, shift);
      return rotated;
      }

   /**
    * Sets all elements of the array to <i>0</i>.
    * @param   array   the array to zeroize.
    */
   protected static void zeroize(byte[] array) {
      Arrays.memset(array, (byte) 0);
      }

   /**
    * Sets all elements of the array to <i>0</i>.
    * @param   array   the array to zeroize.
    */
   protected static void zeroize(int[] array) {
      Arrays.memset(array, 0);
      }

   /**
    * Sets all elements of the array to <i>0</i>.
    * @param   array   the array to zeroize.
    */
   protected static void zeroize(long[] array) {
      Arrays.memset(array, 0L);
      }

   /**
    * Constructs an <code>int</code> from the specified bytes.
    * @param   array   the source of bytes.
    * @param   most   index in the source of bits 31 to 24.
    * @param   more   index in the source of bits 23 to 16.
    * @param   less   index in the source of bits 15 to 8.
    * @param   least   index in the source of bits 7 to 0.
    * @return   the constructed <code>int</code>.
    */
   protected static int makeInt(byte[] array, int most, int more, int less, int least) {
      int i = Bits.makeInt(array[most], array[more], array[less], array[least]);
      return i;
      }

   /**
    * Constructs a <code>long</code> from the specified bytes.
    * @param   array   the source of bytes.
    * @param   b1   index in the source of bits 63 to 56.
    * @param   b2   index in the source of bits 55 to 48.
    * @param   b3   index in the source of bits 47 to 40.
    * @param   b4   index in the source of bits 39 to 32.
    * @param   b5   index in the source of bits 31 to 24.
    * @param   b6   index in the source of bits 23 to 16.
    * @param   b7   index in the source of bits 15 to 8.
    * @param   b8   index in the source of bits 7 to 0.
    * @return   the constructed <code>long</code>.
    */
   protected static long makeLong(byte[] array, int b1, int b2, int b3, int b4, int b5, int b6, int b7, int b8) {
      long l = Bits.makeLong(array[b1], array[b2], array[b3], array[b4], array[b5], array[b6], array[b7], array[b8]);
      return l;
      }

   /**
    * Breaks the given <code>int</code> into big-endian bytes.
    * @param i   the <code>int</code> to break.
    * @param array   the destination.
    * @param off   the index of the first byte.
    * @return   <code>off + 4</code>.
    */
   protected static int breakIntBE(int i, byte[] array, int off) {
      Bits.breakInt(i, array, off);
      int new_off = off + 4;
      return new_off;
      }

   /**
    * Breaks the given <code>int</code> into little-endian bytes.
    * @param i   the <code>int</code> to break.
    * @param array   the destination.
    * @param off   the index of the first byte.
    * @return   <code>off + 4</code>.
    */
   protected static int breakIntLE(int i, byte[] array, int off) {
      Bits.breakIntLE(i, array, off);
      int new_off = off + 4;
      return new_off;
      }

   /**
    * Breaks the given <code>long</code> into little-endian bytes.
    * @param l   the <code>long</code> to break.
    * @param array   the destination.
    * @param off   the index of the first byte.
    * @return   <code>off + 8</code>.
    */
   protected static int breakLongLE(long l, byte[] array, int off) {
      Bits.breakLongLE(l, array, off);
      int new_off = off + 8;
      return new_off;
      }

   /**
    * Breaks the given <code>long</code> into big-endian bytes.
    * @param l   the <code>long</code> to break.
    * @param array   the destination.
    * @param off   the index of the first byte.
    * @return   <code>off + 8</code>.
    */
   protected static int breakLongBE(long l, byte[] array, int off) {
      Bits.breakLong(l, array, off);
      int new_off = off + 8;
      return new_off;
      }


//---------------------------
// Error messages
//---------------------------

   /**
    * Gets the formatted error message about a parameter not having a positive
    * value.
    * @param   name   the name of the parameter.
    * @param   value   the value of the parameter.
    * @return   the formatted error message.
    */
   @SuppressWarnings("boxing")
   protected String getParameterNotPositiveErrorMessage(String name, int value) {
      String key = Resources.PARAMETER_NOT_POSITIVE;
      String message = Resources.getLocalizedString(key, name, value);
      return message;
      }

   /**
    * Gets the formatted error message about a {@link AlgorithmParameterSpec}
    * not being of the right class.
    * @param   expected   the expected class.
    * @param   is   the current class.
    * @return   the formatted error message.
    */
   protected String getBadSpecClassErrorMessage(Class<?> expected, Class<?> is) {
      String key = Resources.BAD_SPEC_CLASS;
      String message = Resources.getLocalizedString(key, expected, is);
      return message;
      }

   }
